#
# Copyright (c) 2024-2025 Semgrep Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# version 2.1 as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
# LICENSE for more details.
#
import textwrap

import pytest

from semdep.parsers import yarn


@pytest.mark.quick
@pytest.mark.parametrize(
    "original, expected",
    [
        (
            ("@jridgewell/gen-mapping", "npm:^0.1.0"),
            ("@jridgewell/gen-mapping", "^0.1.0"),
        ),
        (("string-width-cjs", "npm:string-width@^4.2.0"), ("string-width", "^4.2.0")),
        (
            ("@types/ol-ext", "@siedlerchr/types-ol-ext@3.0.6"),
            ("@siedlerchr/types-ol-ext", "3.0.6"),
        ),
        (
            (
                "tailwindcss@npm:@tailwindcss/postcss7-compat",
                "npm:@tailwindcss/postcss7-compat",
            ),
            ("@tailwindcss/postcss7-compat", ""),
        ),
    ],
)
def test_dep_version_pair(original, expected) -> None:
    assert yarn.dep_version_pair(original[0], original[1]) == expected


@pytest.mark.quick
@pytest.mark.parametrize(
    "original, expected",
    [
        (
            '''"@jridgewell/gen-mapping" "npm:^0.1.0"''',
            (('"@jridgewell/gen-mapping"', "npm:^0.1.0"), ""),
        ),
        (
            '''version "2.1.1"''',
            (("version", "2.1.1"), ""),
        ),
        (
            """integrity sha512-Aolwjd7HSC2PyY0fDj/wA/EimQT4HfEnFYNp5s9CQlrdhyvWTtvZ5YzrUPu6R6/1jKiUlxu8bUhkdSnKHNAHMA==""",
            (
                (
                    "integrity",
                    "sha512-Aolwjd7HSC2PyY0fDj/wA/EimQT4HfEnFYNp5s9CQlrdhyvWTtvZ5YzrUPu6R6/1jKiUlxu8bUhkdSnKHNAHMA==",
                ),
                "",
            ),
        ),
    ],
)
def test_key_value1(original, expected) -> None:
    assert yarn.key_value1.parse_partial(original) == expected


@pytest.mark.quick
@pytest.mark.parametrize(
    "original, expected",
    [
        (
            '''"@ampproject/remapping@^2.0.0"''',
            (
                ("@ampproject/remapping", "^2.0.0"),
                "",
            ),  # TODO: Do NOT filter out the NPM org tag (@)
        ),
        ("""bad-lib@0.0.8""", (("bad-lib", "0.0.8"), "")),
        (
            """my-package-without-version-constraint""",
            (("my-package-without-version-constraint", ""), ""),
        ),
        (
            '''"filedep@file:../../correct/path/filedep"''',
            (("filedep", "file:../../correct/path/filedep"), ""),
        ),
        (
            '''"bats@https://github.com/bats-core/bats-core#master"''',
            (("bats", "https://github.com/bats-core/bats-core#master"), ""),
        ),
    ],
)
def test_source1(original, expected) -> None:
    assert yarn.source1.parse_partial(original) == expected


@pytest.mark.quick
def test_yarn_dep1() -> None:
    original = textwrap.dedent(
        """"@ampproject/remapping@^2.0.0":
  version "2.1.1"
  resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.1.tgz"
  integrity sha512-Aolwjd7HSC2PyY0fDj/wA/EimQT4HfEnFYNp5s9CQlrdhyvWTtvZ5YzrUPu6R6/1jKiUlxu8bUhkdSnKHNAHMA==
  dependencies:
    "@jridgewell/gen-mapping" "npm:^0.1.0"
    "@jridgewell/trace-mapping" "npm:^0.3.9"
        """
    )
    expected = (
        (
            1,
            (
                [("@ampproject/remapping", "^2.0.0")],
                {
                    "version": "2.1.1",
                    "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.1.tgz",
                    "checksum": "sha512-Aolwjd7HSC2PyY0fDj/wA/EimQT4HfEnFYNp5s9CQlrdhyvWTtvZ5YzrUPu6R6/1jKiUlxu8bUhkdSnKHNAHMA==",
                    "children": [
                        ("@jridgewell/gen-mapping", "^0.1.0"),
                        ("@jridgewell/trace-mapping", "^0.3.9"),
                    ],
                },
            ),
        ),
        "\n",
    )
    assert yarn.yarn_dep1.parse_partial(original) == expected


@pytest.mark.quick
def test_yarn1() -> None:
    original = textwrap.dedent(
        """# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

"@ampproject/remapping@^2.0.0":
  version "2.1.1"
  resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.1.tgz"
  integrity sha512-Aolwjd7HSC2PyY0fDj/wA/EimQT4HfEnFYNp5s9CQlrdhyvWTtvZ5YzrUPu6R6/1jKiUlxu8bUhkdSnKHNAHMA==

"@jridgewell/gen-mapping@npm:^0.1.0":
  version "0.1.0"
  resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.0.tgz"
  integrity sha512-Aolwjd7HSC2PyY0fDj/wA/EimQT4HfEnFYNp5s9CQlrdhyvWTtvZ5YzrUPu6R6/1jKiUlxu8bUhkdSnKHNAHMA==
  dependencies:
    "@jridgewell/trace-mapping" "^0.3.0"

"saa@^2.0.0", "saa@^3.1.0":
  version "2.1.1"
  resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.1.tgz"
  integrity sha512-Aolwjd7HSC2PyY0fDj/wA/EimQT4HfEnFYNp5s9CQlrdhyvWTtvZ5YzrUPu6R6/1jKiUlxu8bUhkdSnKHNAHMA==
  dependencies:
    "@jridgewell/gen-mapping" "^0.1.0"
    "@jridgewell/trace-mapping" "^0.3.9"
    """
    )
    expected = (
        [
            (
                4,
                (
                    [("@ampproject/remapping", "^2.0.0")],
                    {
                        "version": "2.1.1",
                        "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.1.tgz",
                        "checksum": "sha512-Aolwjd7HSC2PyY0fDj/wA/EimQT4HfEnFYNp5s9CQlrdhyvWTtvZ5YzrUPu6R6/1jKiUlxu8bUhkdSnKHNAHMA==",
                        "children": [],
                    },
                ),
            ),
            (
                9,
                (
                    [("@jridgewell/gen-mapping", "npm:^0.1.0")],
                    {
                        "version": "0.1.0",
                        "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.0.tgz",
                        "checksum": "sha512-Aolwjd7HSC2PyY0fDj/wA/EimQT4HfEnFYNp5s9CQlrdhyvWTtvZ5YzrUPu6R6/1jKiUlxu8bUhkdSnKHNAHMA==",
                        "children": [("@jridgewell/trace-mapping", "^0.3.0")],
                    },
                ),
            ),
            (
                16,
                (
                    [("saa", "^2.0.0"), ("saa", "^3.1.0")],
                    {
                        "version": "2.1.1",
                        "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.1.tgz",
                        "checksum": "sha512-Aolwjd7HSC2PyY0fDj/wA/EimQT4HfEnFYNp5s9CQlrdhyvWTtvZ5YzrUPu6R6/1jKiUlxu8bUhkdSnKHNAHMA==",
                        "children": [
                            ("@jridgewell/gen-mapping", "^0.1.0"),
                            ("@jridgewell/trace-mapping", "^0.3.9"),
                        ],
                    },
                ),
            ),
        ],
        "",
    )
    assert yarn.yarn1.parse_partial(original) == expected


@pytest.mark.quick
@pytest.mark.parametrize(
    "original, expected",
    [
        (
            """@ampproject/remapping@npm:^2.0.0""",
            (("@ampproject/remapping", "^2.0.0"), ""),
        ),
        (
            """@my-scope/my-first-package@my-scope/my-first-package#commit=0b824c650d3a03444dbcf2b27a5f3566f6e41358""",
            (
                (
                    "@my-scope/my-first-package",
                    "my-scope/my-first-package#commit=0b824c650d3a03444dbcf2b27a5f3566f6e41358",
                ),
                "",
            ),
        ),
        (
            """my-third-package@https://github.com/my-org/my-third-package#everything""",
            (
                (
                    "my-third-package",
                    "https://github.com/my-org/my-third-package#everything",
                ),
                "",
            ),
        ),
        (
            """my-package@file:../../deps/my-local-package::locator=my-project%40workspace%3A.""",
            (
                (
                    "my-package",
                    "file:../../deps/my-local-package::locator=my-project%40workspace%3A.",
                ),
                "",
            ),
        ),
        (
            """resolve@patch:resolve@^1.1.7#~builtin<compat/resolve>""",
            (("resolve", "patch:resolve@^1.1.7#~builtin<compat/resolve>"), ""),
        ),
    ],
)
def test_source2(original, expected) -> None:
    assert yarn.source2.parse_partial(original) == expected


@pytest.mark.quick
@pytest.mark.parametrize(
    "original, expected",
    [
        (
            """  checksum: 2987dbebb484727a227f1ce3db90810320986cfb3ffd23e6d1d87f75bbd8e7871b5bc44252822d4d5f048a2d872a5702b2a9bf7bab7e07f087d7f306f0ea6c0a""",
            (
                (
                    "checksum",
                    "2987dbebb484727a227f1ce3db90810320986cfb3ffd23e6d1d87f75bbd8e7871b5bc44252822d4d5f048a2d872a5702b2a9bf7bab7e07f087d7f306f0ea6c0a",
                ),
                "",
            ),
        ),
        ("""  version: 7.18.10""", (("version", "7.18.10"), "")),
        (
            '''  resolution: "@babel/generator@npm:7.18.10"''',
            (
                (
                    "resolution",
                    "@babel/generator@npm:7.18.10",
                ),
                "",
            ),
        ),
    ],
)
def test_key_value2(original, expected) -> None:
    assert yarn.key_value2.parse_partial(original) == expected


@pytest.mark.quick
def test_yarn2() -> None:
    original = textwrap.dedent(
        """# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
  version: 6
  cacheKey: 8

"@babel/generator@npm:^7.17.0, @babel/generator@npm:^7.7.2":
  version: 7.17.0
  resolution: "@babel/generator@npm:7.17.0"
  checksum: 2987dbebb484727a227f1ce3db90810320986cfb3ffd23e6d1d87f75bbd8e7871b5bc44252822d4d5f048a2d872a5702b2a9bf7bab7e07f087d7f306f0ea6c0a
  languageName: node

"@babel/generator@npm:^7.17.0, @babel/generator@npm:^7.7.2":
  version: 7.17.0
  resolution: "@babel/generator@npm:7.17.0"
  dependencies:
    "@babel/types": ^7.17.0
    jsesc: ^2.5.1
    source-map: ^0.5.0
  checksum: 2987dbebb484727a227f1ce3db90810320986cfb3ffd23e6d1d87f75bbd8e7871b5bc44252822d4d5f048a2d872a5702b2a9bf7bab7e07f087d7f306f0ea6c0a
  languageName: node

"@storybook/react-docgen-typescript-plugin@canary, @storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0":
  version: 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0
  resolution: "@storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0"
  dependencies:
    debug: ^4.1.1
    endent: ^2.0.1
    find-cache-dir: ^3.3.1
    flat-cache: ^3.0.4
    micromatch: ^4.0.2
    react-docgen-typescript: ^2.1.1
    tslib: ^2.0.0
  peerDependencies:
    typescript: ">= 3.x"
    webpack: ">= 4"
  checksum: 91a3015d384e93d9ffb4def904cad51218eb1a9eaf504c758083f2988a97d8bf8748bc280aa629864eb26fd9f7fc05bd087df95383d719e0c914c722016804b9
  languageName: node
  linkType: hard

  """
    )
    expected = (
        [
            (
                8,
                (
                    [("@babel/generator", "^7.17.0"), ("@babel/generator", "^7.7.2")],
                    {
                        "version": "7.17.0",
                        "resolved": None,
                        "checksum": "2987dbebb484727a227f1ce3db90810320986cfb3ffd23e6d1d87f75bbd8e7871b5bc44252822d4d5f048a2d872a5702b2a9bf7bab7e07f087d7f306f0ea6c0a",
                        "children": [],
                    },
                ),
            ),
            (
                14,
                (
                    [("@babel/generator", "^7.17.0"), ("@babel/generator", "^7.7.2")],
                    {
                        "version": "7.17.0",
                        "resolved": None,
                        "children": [
                            ("@babel/types", "^7.17.0"),
                            ("jsesc", "^2.5.1"),
                            ("source-map", "^0.5.0"),
                        ],
                        "checksum": None,
                    },
                ),
            ),
            (
                24,
                (
                    [
                        ("@storybook/react-docgen-typescript-plugin", "canary"),
                        (
                            "@storybook/react-docgen-typescript-plugin",
                            "1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0",
                        ),
                    ],
                    {
                        "version": "1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0",
                        "resolved": None,
                        "children": [
                            ("debug", "^4.1.1"),
                            ("endent", "^2.0.1"),
                            ("find-cache-dir", "^3.3.1"),
                            ("flat-cache", "^3.0.4"),
                            ("micromatch", "^4.0.2"),
                            ("react-docgen-typescript", "^2.1.1"),
                            ("tslib", "^2.0.0"),
                        ],
                        "checksum": None,
                    },
                ),
            ),
        ],
        "",
    )
    assert yarn.yarn2.parse_partial(original) == expected


@pytest.mark.quick
def test_yarn2_missing_descriptor() -> None:
    original = textwrap.dedent(
        """# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
  version: 6
  cacheKey: 8

"tslib@npm:^2.2.0":
  version: 2.2.0
  resolution: "tslib@npm:2.2.0"
  checksum: 2987dbebb484727a227f1ce3db90810320986cfb3ffd23e6d1d87f75bbd8e7871b5bc44252822d4d5f048a2d872a5702b2a9bf7bab7e07f087d7f306f0ea6c0a
  languageName: node

"@babel/generator@npm:^7.17.0, @babel/generator@npm:^7.7.2":
  version: 7.17.0
  resolution: "@babel/generator@npm:7.17.0"
  dependencies:
    "@babel/types": ^7.17.0
    jsesc: ^2.5.1
    source-map: ^0.5.0
    tslib: ^2.0.0
  checksum: 2987dbebb484727a227f1ce3db90810320986cfb3ffd23e6d1d87f75bbd8e7871b5bc44252822d4d5f048a2d872a5702b2a9bf7bab7e07f087d7f306f0ea6c0a
  languageName: node

"@storybook/react-docgen-typescript-plugin@canary, @storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0":
  version: 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0
  resolution: "@storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0"
  dependencies:
    debug: ^4.1.1
    endent: ^2.0.1
    find-cache-dir: ^3.3.1
    flat-cache: ^3.0.4
    micromatch: ^4.0.2
    react-docgen-typescript: ^2.1.1
    tslib: ^2.0.0
  peerDependencies:
    typescript: ">= 3.x"
    webpack: ">= 4"
  checksum: 91a3015d384e93d9ffb4def904cad51218eb1a9eaf504c758083f2988a97d8bf8748bc280aa629864eb26fd9f7fc05bd087df95383d719e0c914c722016804b9
  languageName: node
  linkType: hard

  """
    )
    expected = (
        [
            (
                8,
                (
                    [("tslib", "^2.2.0")],
                    {
                        "version": "2.2.0",
                        "resolved": None,
                        "checksum": "2987dbebb484727a227f1ce3db90810320986cfb3ffd23e6d1d87f75bbd8e7871b5bc44252822d4d5f048a2d872a5702b2a9bf7bab7e07f087d7f306f0ea6c0a",
                        "children": [],
                    },
                ),
            ),
            (
                14,
                (
                    [("@babel/generator", "^7.17.0"), ("@babel/generator", "^7.7.2")],
                    {
                        "version": "7.17.0",
                        "resolved": None,
                        "children": [
                            ("@babel/types", "^7.17.0"),
                            ("jsesc", "^2.5.1"),
                            ("source-map", "^0.5.0"),
                            ("tslib", "^2.0.0"),
                        ],
                        "checksum": None,
                    },
                ),
            ),
            (
                25,
                (
                    [
                        ("@storybook/react-docgen-typescript-plugin", "canary"),
                        (
                            "@storybook/react-docgen-typescript-plugin",
                            "1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0",
                        ),
                    ],
                    {
                        "version": "1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0",
                        "resolved": None,
                        "children": [
                            ("debug", "^4.1.1"),
                            ("endent", "^2.0.1"),
                            ("find-cache-dir", "^3.3.1"),
                            ("flat-cache", "^3.0.4"),
                            ("micromatch", "^4.0.2"),
                            ("react-docgen-typescript", "^2.1.1"),
                            ("tslib", "^2.0.0"),
                        ],
                        "checksum": None,
                    },
                ),
            ),
        ],
        "",
    )
    assert yarn.yarn2.parse_partial(original) == expected
